﻿//---------------------------------------------------------------------------
// <copyright file="RibbonGroup.cs" company="Microsoft Corporation">
//     Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------

namespace Microsoft.Windows.Controls.Ribbon
{
    #region Using declarations

    using System;
    using System.Collections;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Input;
    using System.Windows.Markup;
    using System.Windows.Media;
    
    #endregion

    /// <summary>
    ///   A RibbonGroup represents a logical group of controls as they appear on
    ///   a RibbonTab.  These groups resize independently of one another to layout
    ///   their controls in the largest available space.
    /// </summary>
    [ContentProperty("Controls")]
    [TemplatePart(Name = "PART_RibbonControlsHostBorder", Type = typeof(Border))]
    [TemplatePart(Name = "PART_DialogLauncher", Type = typeof(RibbonDialogLauncher))]
    public class RibbonGroup : Control, ICommandSource
    {
        #region Fields

        /// <summary>
        ///   The template name of the Controls host border where we insert the RibbonGroup's Panel.
        /// </summary>
        private const string RibbonControlsHostName = "PART_RibbonControlsHostBorder";

        /// <summary>
        ///   The template name of the RibbonGroup's dialog launcher.
        /// </summary>
        private const string DialogLauncherTemplateName = "PART_DialogLauncher";

        /// <summary>
        ///   Backing store for the Controls property.
        /// </summary>
        private Collection<IRibbonControl> _controls;

        /// <summary>
        ///   Backing store for the GroupSizeDefinitionsInternal property.
        /// </summary>
        private Collection<RibbonGroupSizeDefinition> _groupSizeDefinitionsInternal;

        /// <summary>
        ///   Backing store for the ItemsPanelInternal property.
        /// </summary>
        private Panel _itemsPanelInternal;

        /// <summary>
        ///   The current position of the RibbonGroup ControlSizeDefinition index.
        /// </summary>
        private int _sizeDefinitionIndex = 0;

        #endregion

        #region Constructors

        /// <summary>
        ///   Initializes static members of the RibbonGroup class.  This overrides the
        ///   default style and hooks up the Click event to the group's RibbonDialogLauncher.
        /// </summary>
        static RibbonGroup()
        {
            Type ownerType = typeof(RibbonGroup);

            DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType));
            EventManager.RegisterClassHandler(ownerType, ButtonBase.ClickEvent, new RoutedEventHandler(RibbonGroup.OnDialogLauncherClicked));
        }

        /// <summary>
        ///   Initializes a new instance of the RibbonGroup class.
        /// </summary>
        public RibbonGroup()
        {
            // TODO - Is there a better way to do this?
            CoerceValue(ItemsPanelProperty);
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets a value indicating whether or not the RibbonGroup is displayed in a "Collapsed"
        ///     state. In this state the RibbonGroup looks like a drop-down button.
        /// </summary>
        public bool IsCollapsed
        {
            get { return (bool)GetValue(IsCollapsedProperty); }
            internal set { SetValue(IsCollapsedPropertyKey, value); }
        }

        /// <summary>
        ///   Key for the IsCollapsed DependencyProperty.
        /// </summary>
        private static readonly DependencyPropertyKey IsCollapsedPropertyKey =
                    DependencyProperty.RegisterReadOnly(
                            "IsCollapsed",
                            typeof(bool),
                            typeof(RibbonGroup),
                            new FrameworkPropertyMetadata(false));

        /// <summary>
        ///   Gets or sets the Command that will be executed when the command source is invoked.
        /// </summary>
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for CommandProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty CommandProperty =
                    DependencyProperty.Register(
                            "Command",
                            typeof(ICommand),
                            typeof(RibbonGroup),
                            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));

        /// <summary>
        ///   Gets or sets a user defined data value that can be passed to the command when it is executed.
        /// </summary>
        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for CommandParameterProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty CommandParameterProperty =
                    DependencyProperty.Register(
                            "CommandParameter",
                            typeof(object),
                            typeof(RibbonGroup),
                            new FrameworkPropertyMetadata(null));

        /// <summary>
        ///   Gets or sets the object that the command is being executed on.
        /// </summary>
        public IInputElement CommandTarget
        {
            get { return (IInputElement)GetValue(CommandTargetProperty); }
            set { SetValue(CommandTargetProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for CommandTargetProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
            "CommandTarget",
            typeof(IInputElement),
            typeof(RibbonGroup),
            new FrameworkPropertyMetadata(null));

        /// <summary>
        ///   Gets the Controls property is a collection of all IRibbonControls in the RibbonGroup.
        /// </summary>
        public Collection<IRibbonControl> Controls
        {
            get
            {
                if (_controls == null)
                {
                    ObservableCollection<IRibbonControl> collection = new ObservableCollection<IRibbonControl>();
                    collection.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnControlsCollectionChanged);
                    _controls = collection;
                }

                return _controls;
            }
        }

        /// <summary>
        ///     Gets or sets the GroupSizeDefinitions property.  This is a collection of RibbonGroupSizeDefinitions which describe
        ///     how the controls in the RibbonGroup should be sized for different size variations of the RibbonGroup itself.
        /// </summary>
        public Collection<RibbonGroupSizeDefinition> GroupSizeDefinitions
        {
            get { return (Collection<RibbonGroupSizeDefinition>)GetValue(GroupSizeDefinitionsProperty); }
            set { SetValue(GroupSizeDefinitionsProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for GroupSizeDefinitionsProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty GroupSizeDefinitionsProperty =
                    DependencyProperty.Register(
                            "GroupSizeDefinitions", 
                            typeof(Collection<RibbonGroupSizeDefinition>), 
                            typeof(RibbonGroup),
                            new FrameworkPropertyMetadata(null, new CoerceValueCallback(CoerceGroupSizeDefinitions)));

        /// <summary>
        ///   Gets or sets the ItemsPanel property.  This allows the developer to define a
        ///   Panel to lay out the RibbonGroup's children.
        /// </summary>
        public Panel ItemsPanel
        {
            get { return (Panel)GetValue(ItemsPanelProperty); }
            set { SetValue(ItemsPanelProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for ItemsPanelProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty ItemsPanelProperty =
                    DependencyProperty.Register(
                            "ItemsPanel",
                            typeof(Panel),
                            typeof(RibbonGroup),
                            new FrameworkPropertyMetadata(null, new CoerceValueCallback(CoerceItemsPanel)));

        /// <summary>
        ///   Gets or sets a value indicating whether the RibbonGroup's RibbonDialogLauncher is displayed.
        /// </summary>
        public bool HasDialogLauncher
        {
            get { return (bool)GetValue(HasDialogLauncherProperty); }
            set { SetValue(HasDialogLauncherProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for HasDialogLauncherProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty HasDialogLauncherProperty =
                    DependencyProperty.Register(
                            "HasDialogLauncher",
                            typeof(bool),
                            typeof(RibbonGroup),
                            new FrameworkPropertyMetadata(false));

        /// <summary>
        ///   Using a DependencyProperty as the backing store for IsCollapsedProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty IsCollapsedProperty =
                IsCollapsedPropertyKey.DependencyProperty;

        /// <summary>
        ///   Gets the ControlSizeDefinition attached property.
        /// </summary>
        /// <param name="obj">The object to get the ControlSizeDefinition property from.</param>
        /// <returns>The value of the ControlSizeDefintion.</returns>
        public static RibbonControlSizeDefinition GetControlSizeDefinition(DependencyObject obj)
        {
            return (RibbonControlSizeDefinition)obj.GetValue(ControlSizeDefinitionProperty);
        }

        /// <summary>
        ///   Sets the ControlSizeDefintion attached property.
        /// </summary>
        /// <param name="obj">The object to set the ControlSizeDefinition property on.</param>
        /// <param name="value">The value of the ControlSizeDefintion.</param>
        public static void SetControlSizeDefinition(DependencyObject obj, RibbonControlSizeDefinition value)
        {
            obj.SetValue(ControlSizeDefinitionProperty, value);
        }

        /// <summary>
        ///   Using an attached DependencyProperty as the backing store for ControlSizeDefinitionProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty ControlSizeDefinitionProperty = DependencyProperty.RegisterAttached(
            "ControlSizeDefinition",
            typeof(RibbonControlSizeDefinition),
            typeof(RibbonGroup),
            new FrameworkPropertyMetadata(new RibbonControlSizeDefinition() { ImageSize = RibbonImageSize.Large, IsLabelVisible = true }, new PropertyChangedCallback(OnControlSizeDefinitionChanged)));

        #endregion

        #region Protected Properties

        /// <summary>
        ///   Gets an enumerator for the logical children of the RibbonGroup.  This is
        ///   what hooks up the logical tree.
        /// </summary>
        protected override IEnumerator LogicalChildren
        {
            get { return this.Controls.GetEnumerator(); }
        }

        #endregion

        #region Private Properties

        /// <summary>
        ///   Gets or sets the GroupSizeDefinitionsInternal property.  This property supplies default
        ///   values for the GroupSizeDefinitions property if no other value has been assigned.
        /// </summary>
        private Collection<RibbonGroupSizeDefinition> GroupSizeDefinitionsInternal
        {
            get
            {
                return _groupSizeDefinitionsInternal;
            }

            set
            {
                _groupSizeDefinitionsInternal = value;
                this.CoerceValue(RibbonGroup.GroupSizeDefinitionsProperty);
            }
        }

        /// <summary>
        ///     Gets or sets the ItemsPanelInternal property.  This is set by default at construction
        ///     time for RibbonGroup. This property supplies the value for the public ItemsPanel
        ///     property if no actual value is assigned.
        /// </summary>
        private Panel ItemsPanelInternal
        {
            get 
            {
                if (_itemsPanelInternal == null)
                {
                    _itemsPanelInternal = new RibbonGroupPanel();
                }

                return _itemsPanelInternal;
            }

            set
            {
                _itemsPanelInternal = value;
                this.CoerceValue(RibbonGroup.ItemsPanelProperty);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///   Invoked when the control's template is applied.  This adds the group's
        ///   controls to the host panel, and hooks up the RibbonDialogLauncher.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Disconnect the old panel
            if (this.ItemsPanel != null)
            {
                this.ItemsPanel.Children.Clear();
                if (this.ItemsPanel.Parent != null)
                {
                    Border oldHost = (Border)this.ItemsPanel.Parent;
                    oldHost.Child = null;
                }
            }

            Border controlsHost = this.GetTemplateChild(RibbonGroup.RibbonControlsHostName) as Border;
            controlsHost.Child = this.ItemsPanel;

            // Add Controls to the new panel
            if (this.ItemsPanel != null)
            {
                foreach (IRibbonControl control in this.Controls)
                {
                    FrameworkElement fe = (FrameworkElement)control;
                    this.ItemsPanel.Children.Add(fe);
                }
            }

            if (this.HasDialogLauncher)
            {
                RibbonDialogLauncher launcher = this.GetTemplateChild(RibbonGroup.DialogLauncherTemplateName) as RibbonDialogLauncher;
                if (launcher != null)
                {
                    launcher.ToolTip = new RibbonToolTip(this.Command as RibbonCommand);
                }
            }

            RibbonGroupSizeDefinition initialSizes = this.GroupSizeDefinitions[_sizeDefinitionIndex];
            this.ApplyGroupSizeDefinition(initialSizes);
        }

        #endregion

        #region Internal Methods

        /// <summary>
        ///     Called whenever the RibbonGroup should try to increase its size.  Moves the size
        ///     definition index counter to the next largest group size definition and applies it.
        /// </summary>
        /// <returns>Returns true if the resize was successful, false otherwise.</returns>
        internal bool IncreaseGroupSize()
        {
            bool resizeSuccessful = false;

            if (_sizeDefinitionIndex > 0 && this.GroupSizeDefinitions.Count > 0)
            {
                _sizeDefinitionIndex--;
                RibbonGroupSizeDefinition newSizes = this.GroupSizeDefinitions[_sizeDefinitionIndex];
                this.ApplyGroupSizeDefinition(newSizes);
                this.IsCollapsed = false;

                resizeSuccessful = true;
            }

            return resizeSuccessful;
        }

        /// <summary>
        ///     Called whenever the RibbonGroup should try to decrease its size.  Moves the size
        ///     definition index counter to the next smallest group size definition and applies it.
        /// </summary>
        /// <returns>Returns true if the resize was successful, false otherwise.</returns>
        internal bool DecreaseGroupSize()
        {
            bool resizeSuccessful = false;
            int numDefinitions = this.GroupSizeDefinitions.Count;

            if (_sizeDefinitionIndex < numDefinitions - 1)
            {
                _sizeDefinitionIndex++;
                RibbonGroupSizeDefinition newSizes = this.GroupSizeDefinitions[_sizeDefinitionIndex];
                if (newSizes.IsCollapsed == true)
                {
                    // If the IsPopup property is set to true, the default behavior is for the
                    // largest variant to appear in the popup.
                    this.ApplyGroupSizeDefinition(this.GroupSizeDefinitions[0]);
                    this.IsCollapsed = true;
                }
                else
                {
                    this.ApplyGroupSizeDefinition(newSizes);
                }

                resizeSuccessful = true;
            }

            return resizeSuccessful;
        }

        /// <summary>
        ///     Applies the RibbonGroupSizeDefinition by setting each of the RibbonGroup's control sizes.
        /// </summary>
        /// <param name="definition">The definition to apply.</param>
        internal void ApplyGroupSizeDefinition(RibbonGroupSizeDefinition definition)
        {
            definition.Seal();
            int numDefinedSizes = definition.ControlSizeDefinitions.Count;
            int i = 0;
            foreach (DependencyObject d in this.Controls)
            {
                if (i < numDefinedSizes)
                {
                    RibbonControlSizeDefinition def = definition.ControlSizeDefinitions[i++];
                    RibbonGroup.SetControlSizeDefinition(d, def);
                }
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        ///     Fires the command associated with this RibbonGroup when the RibbonDialogLauncher is clicked.
        /// </summary>
        /// <param name="sender">The RibbonGroup whose dialog launcher was clicked.</param>
        /// <param name="e">The event data.</param>
        private static void OnDialogLauncherClicked(object sender, RoutedEventArgs e)
        {
            RibbonGroup group = sender as RibbonGroup;
            RibbonDialogLauncher launcher = e.OriginalSource as RibbonDialogLauncher;

            if (group != null && launcher != null && launcher.TemplatedParent == group)
            {
                CommandHelpers.ExecuteCommandSource(null, group);
            }
        }

        /// <summary>
        ///   If the Command property changes, we need to coerce some RibbonControl
        ///   specific properties to update their 'back-up' values coming from Command.
        /// </summary>
        /// <param name="d">The RibbonGroup on which the Command property has changed value.</param>
        /// <param name="e">
        ///   Event data that is issued by any event that tracks changes to the effective value of this property.
        /// </param>
        private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RibbonGroup comboBox = (RibbonGroup)d;

            comboBox.CoerceValue(ToolTipProperty);
        }

        /// <summary>
        ///     Coerces the GroupSizeDefinitions DependencyProperty to either use its non-null developer assigned value,
        ///     or the default internal GroupSizeDefinitions property value.
        /// </summary>
        /// <param name="d">The RibbonGroup whose GroupSizeDefinitions property changed.</param>
        /// <param name="baseValue">The new value of the GroupSizeDefinitions property, prior to any coercion attempt.</param>
        /// <returns>The coerced value of the GroupSizeDefinitions property.</returns>
        private static object CoerceGroupSizeDefinitions(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                RibbonGroup group = (RibbonGroup)d;
                return group.GroupSizeDefinitionsInternal;
            }
            else
            {
                return baseValue;
            }
        }

        /// <summary>
        ///     Coerces the ItemsPanel DependencyProperty to either use its non-null developer assigned value, or
        ///     the default internal ItemsPanel property value.
        /// </summary>
        /// <param name="d">The RibbonGroup whose ItemsPanel property is being coerced.</param>
        /// <param name="baseValue">The new value of the ItemsPanel property, prior to any coercion attempt.</param>
        /// <returns>The coerced value of the ItemsPanel property.</returns>
        private static object CoerceItemsPanel(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                RibbonGroup group = (RibbonGroup)d;
                return group.ItemsPanelInternal;
            }
            else
            {
                return baseValue;
            }
        }

        /// <summary>
        ///     When the ControlSizeDefinition property changes we need to invalidate the parent chain measure so that
        ///     the RibbonGroupsContainer can calculate the new size within the same MeasureOverride call.  This property
        ///     usually changes from RibbonGroupsContainer.MeasureOverride.
        /// </summary>
        /// <param name="o">The Visual on which the ControlSizeDefinition changed.</param>
        /// <param name="e">The event data.</param>
        private static void OnControlSizeDefinitionChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Visual visual = o as Visual;
            while (visual != null)
            {
                UIElement uiElement = visual as UIElement;
                if (uiElement != null)
                {
                    if (uiElement is RibbonGroupsContainer)
                    {
                        break;
                    }

                    uiElement.InvalidateMeasure();
                }

                visual = VisualTreeHelper.GetParent(visual) as Visual;
            }
        }

        /// <summary>
        ///     Whenever the Controls collection changes, we update the internal GroupSizeDefinitions
        ///     property to reflect the number of controls in the group.
        /// </summary>
        /// <param name="sender">The RibbonGroup's controls collection.</param>
        /// <param name="e">The event data.</param>
        private void OnControlsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (this.Controls.Count)
            {
                case 1:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.OneButton;
                    break;
                case 2:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.TwoButtons;
                    break;
                case 3:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.ThreeButtons;
                    break;
                case 4:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.FourButtons;
                    break;
                case 5:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.FiveButtons;
                    break;
                case 6:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.SixButtons;
                    break;
                case 7:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.SevenButtons;
                    break;
                case 8:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.EightButtons;
                    break;
                case 9:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.NineButtons;
                    break;
                case 10:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.TenButtons;
                    break;
                case 11:
                    this.GroupSizeDefinitionsInternal = RibbonGroupSizeDefinitionCollection.ElevenButtons;
                    break;
            }
        }

        #endregion
    }
}